﻿// -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	SmartLib
//  File name:	Logger.cs
//  Developer:	Igor Solomatov
//  Created:	04.11.2012
// 
// -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace Smart.Loggers
{
	/// <summary>
	/// Logger
	/// </summary>
	public class Logger
	{
		public static Logger Current = new Logger("iSharePoint");

		/// <summary>
		/// Logger providers
		/// </summary>
		public readonly List<ILoggerProvider> Providers;

		/// <summary>
		/// Default category
		/// </summary>
		public readonly string DefaultCategory;

		/// <summary>
		/// Logger
		/// </summary>
		public Logger(string defaultCategory)
		{
			Providers = new List<ILoggerProvider>();

			DefaultCategory = defaultCategory;
		}

		/// <summary>
		/// Register additional <paramref name="provider"/>
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="provider"/> is null</exception>
		public void Register(ILoggerProvider provider)
		{
			if (provider == null) throw new ArgumentNullException("provider");

			if (Providers.Contains(provider)) return;

			Providers.Add(provider);
			Providers.Sort((first, second) => second.Priority - first.Priority);
		}

		/// <summary>
		/// Unregister <paramref name="provider"/>
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="provider"/> is null</exception>
		public void Unregister(ILoggerProvider provider)
		{
			if (provider == null) throw new ArgumentNullException("provider");

			Providers.Remove(provider);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(MessageSeverity severity, string module, string message, Exception x)
		{
			Log(DefaultCategory, severity, module, message, x);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string module, string message, Exception x)
		{
			Log(DefaultCategory, MessageSeverity.Error, module, message, x);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string module, Exception x)
		{
			Log(DefaultCategory, MessageSeverity.Error, module, null, x);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string category, string module, string message, Exception x)
		{
			Log(category, MessageSeverity.Error, module, message, x);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string category, MessageSeverity severity, string module, string message)
		{
			Log(category, severity, module, message, null);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string category, string module, string message)
		{
			Log(category, MessageSeverity.Trace, module, message, null);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(MessageSeverity severity, string module, string message)
		{
			Log(DefaultCategory, severity, module, message, null);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(MessageSeverity severity, string message)
		{
			Log(DefaultCategory, severity, null, message, null);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(MessageSeverity severity, string module, Exception x)
		{
			Log(DefaultCategory, severity, module, null, x);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string module, string message)
		{
			Log(DefaultCategory, MessageSeverity.Trace, module, message, null);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string message)
		{
			Log(DefaultCategory, MessageSeverity.Trace, null, message, null);
		}

		/// <summary>
		/// Logs message
		/// </summary>
		public void Log(string category, MessageSeverity severity, string module, string message, Exception x)
		{
			var fullMessage = GetFullMessage(category, severity, module, message, x);

			var providers = Providers;

			for (int i = 0, iMax = providers.Count; i < iMax; i++)
			{
				var provider = providers[i];

				if (severity < provider.MinimalSeverity) continue;

				provider.Log(category, severity, module, message, x, fullMessage);
			}
		}

		protected string GetFullMessage(string category, MessageSeverity severity, string module, string message, Exception x)
		{
			var res = new StringBuilder();

			if (!String.IsNullOrEmpty(category))
				res.Append("{").Append(category).Append("} ");

			if (!String.IsNullOrEmpty(module))
				res.Append("[").Append(module).Append("] ");

			if (!String.IsNullOrEmpty(message))
				res.Append(message).AppendLine();

			try
			{
				var notFirst = false;
				var cx = x;

				while (cx != null)
				{
					if (notFirst)
					{
						res.Append("caused by ");
					}
					else
					{
						notFirst = true;

						if (!String.IsNullOrEmpty(message))
							res.Append("caused by ");
					}

					res.Append(cx.GetType().Name).Append(": ").Append(cx.Message).Append(" in ").Append(cx.Source).AppendLine();

					cx = x.InnerException;
				}

				if (severity == MessageSeverity.Error)
				{
					// Add stack trace
					res.AppendLine("calling stack trace:");

					FormatStackTrace(res, new StackTrace(2, true));

					notFirst = false;
					cx = x;

					while (cx != null)
					{
						if (notFirst)
						{
							res.Append("caused by ");
						}
						else
						{
							notFirst = true;

							if (!String.IsNullOrEmpty(message))
								res.Append("caused by ");
						}

						res.Append(cx.GetType().Name).Append(": ").Append(cx.Message).Append(" in ").Append(cx.Source).AppendLine().AppendLine("exception stack trace:");

						FormatStackTrace(res, new StackTrace(cx, true));

						cx = x.InnerException;
					}

				}
			}
			catch (Exception ix)
			{
				Trace.WriteLine("Logger.GetFullMessage." + ix.GetType().Name + ": failed, " + ix.Message);
			}

			return res.ToString();
		}

		protected void FormatStackTrace(StringBuilder res, StackTrace stackTrace)
		{
			for (int i = 0, iCnt = stackTrace.FrameCount; i < iCnt; i++)
			{
				var sf = stackTrace.GetFrame(i);

				var method = sf.GetMethod();
				var methodType = method.DeclaringType;

				var lineNo = sf.GetFileLineNumber();
				var columnNo = sf.GetFileColumnNumber();

				res.Append("at ").Append(methodType != null ? methodType.FullName : "<Unknown>").Append(":").Append(method.Name);

				if (lineNo > 0 || columnNo > 0)
					res.Append(" (").Append(lineNo).Append(":").Append(columnNo).Append(" in ").Append(Path.GetFileName(sf.GetFileName())).Append(")");

				res.AppendLine();
			}
		}
	}
}